/*
 * Copyright (c) 2006 Jakub Vana
 * Copyright (c) 2012 Martin Decky
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup kernel_generic
 * @{
 */
/** @file
 */

#include <arch/asm.h>
#include <assert.h>
#include <errno.h>
#include <macros.h>
#include <mm/slab.h>
#include <stdio.h>
#include <stdlib.h>
#include <synch/mutex.h>
#include <syscall/copy.h>
#include <sysinfo/sysinfo.h>

/** Maximal sysinfo path length */
#define SYSINFO_MAX_PATH  2048

bool fb_exported = false;

/** Global sysinfo tree root item */
static sysinfo_item_t *global_root = NULL;

/** Sysinfo SLAB cache */
static slab_cache_t *sysinfo_item_cache;

/** Sysinfo lock */
static MUTEX_INITIALIZE(sysinfo_lock, MUTEX_PASSIVE);

/** Sysinfo item constructor
 *
 */
_NO_TRACE static errno_t sysinfo_item_constructor(void *obj, unsigned int kmflag)
{
	sysinfo_item_t *item = (sysinfo_item_t *) obj;

	item->name = NULL;
	item->val_type = SYSINFO_VAL_UNDEFINED;
	item->subtree_type = SYSINFO_SUBTREE_NONE;
	item->subtree.table = NULL;
	item->next = NULL;

	return EOK;
}

/** Sysinfo item destructor
 *
 * Note that the return value is not perfectly correct
 * since more space might get actually freed thanks
 * to the disposal of item->name
 *
 */
_NO_TRACE static size_t sysinfo_item_destructor(void *obj)
{
	sysinfo_item_t *item = (sysinfo_item_t *) obj;

	if (item->name != NULL)
		free(item->name);

	return 0;
}

/** Initialize sysinfo subsystem
 *
 * Create SLAB cache for sysinfo items.
 *
 */
void sysinfo_init(void)
{
	sysinfo_item_cache = slab_cache_create("sysinfo_item_t",
	    sizeof(sysinfo_item_t), 0, sysinfo_item_constructor,
	    sysinfo_item_destructor, SLAB_CACHE_MAGDEFERRED);
}

/** Recursively find an item in sysinfo tree
 *
 * Should be called with sysinfo_lock held.
 *
 * @param name    Current sysinfo path suffix.
 * @param subtree Current sysinfo (sub)tree root item.
 * @param ret     If the return value is NULL, this argument
 *                can be set either to NULL (i.e. no item was
 *                found and no data was generated) or the
 *                original pointer is used to store the value
 *                generated by a generated subtree function.
 * @param dry_run Do not actually get any generated
 *                binary data, just calculate the size.
 *
 * @return Found item or NULL if no item in the fixed tree
 *         was found (N.B. ret).
 *
 */
_NO_TRACE static sysinfo_item_t *sysinfo_find_item(const char *name,
    sysinfo_item_t *subtree, sysinfo_return_t **ret, bool dry_run)
{
	assert(subtree != NULL);

	sysinfo_item_t *cur = subtree;

	/* Walk all siblings */
	while (cur != NULL) {
		size_t i = 0;

		/* Compare name with path */
		while ((cur->name[i] != 0) && (name[i] == cur->name[i]))
			i++;

		/* Check for perfect name and path match */
		if ((name[i] == 0) && (cur->name[i] == 0))
			return cur;

		/* Partial match up to the delimiter */
		if ((name[i] == '.') && (cur->name[i] == 0)) {
			/* Look into the subtree */
			switch (cur->subtree_type) {
			case SYSINFO_SUBTREE_TABLE:
				/* Recursively find in subtree */
				return sysinfo_find_item(name + i + 1,
				    cur->subtree.table, ret, dry_run);
			case SYSINFO_SUBTREE_FUNCTION:
				/* Get generated data */
				if (ret != NULL)
					**ret = cur->subtree.generator.fn(name + i + 1,
					    dry_run, cur->subtree.generator.data);

				return NULL;
			default:
				/* Not found, no data generated */
				if (ret != NULL)
					*ret = NULL;

				return NULL;
			}
		}

		cur = cur->next;
	}

	/* Not found, no data generated */
	if (ret != NULL)
		*ret = NULL;

	return NULL;
}

/** Recursively create items in sysinfo tree
 *
 * Should be called with sysinfo_lock held.
 *
 * @param name     Current sysinfo path suffix.
 * @param psubtree Pointer to an already existing (sub)tree root
 *                 item or where to store a new tree root item.
 *
 * @return Existing or newly allocated sysinfo item or NULL
 *         if the current tree configuration does not allow to
 *         create a new item.
 *
 */
_NO_TRACE static sysinfo_item_t *sysinfo_create_path(const char *name,
    sysinfo_item_t **psubtree)
{
	assert(psubtree != NULL);

	if (*psubtree == NULL) {
		/* No parent */

		size_t i = 0;

		/* Find the first delimiter in name */
		while ((name[i] != 0) && (name[i] != '.'))
			i++;

		*psubtree =
		    (sysinfo_item_t *) slab_alloc(sysinfo_item_cache, FRAME_ATOMIC);
		if (!*psubtree)
			return NULL;

		/* Fill in item name up to the delimiter */
		(*psubtree)->name = str_ndup(name, i);
		if (!(*psubtree)->name) {
			slab_free(sysinfo_item_cache, *psubtree);
			return NULL;
		}

		/* Create subtree items */
		if (name[i] == '.') {
			(*psubtree)->subtree_type = SYSINFO_SUBTREE_TABLE;
			sysinfo_item_t *item = sysinfo_create_path(name + i + 1,
			    &((*psubtree)->subtree.table));
			if (!item) {
				free((*psubtree)->name);
				slab_free(sysinfo_item_cache, *psubtree);
			}
			return item;
		}

		/* No subtree needs to be created */
		return *psubtree;
	}

	sysinfo_item_t *cur = *psubtree;

	/* Walk all siblings */
	while (cur != NULL) {
		size_t i = 0;

		/* Compare name with path */
		while ((cur->name[i] != 0) && (name[i] == cur->name[i]))
			i++;

		/*
		 * Check for perfect name and path match
		 * -> item is already present.
		 */
		if ((name[i] == 0) && (cur->name[i] == 0))
			return cur;

		/* Partial match up to the delimiter */
		if ((name[i] == '.') && (cur->name[i] == 0)) {
			switch (cur->subtree_type) {
			case SYSINFO_SUBTREE_NONE:
				/* No subtree yet, create one */
				cur->subtree_type = SYSINFO_SUBTREE_TABLE;
				return sysinfo_create_path(name + i + 1,
				    &(cur->subtree.table));
			case SYSINFO_SUBTREE_TABLE:
				/* Subtree already created, add new sibling */
				return sysinfo_create_path(name + i + 1,
				    &(cur->subtree.table));
			default:
				/*
				 * Subtree items handled by a function, this
				 * cannot be overriden by a constant item.
				 */
				return NULL;
			}
		}

		/*
		 * No match and no more siblings to check
		 * -> create a new sibling item.
		 */
		if (cur->next == NULL) {
			/* Find the first delimiter in name */
			i = 0;
			while ((name[i] != 0) && (name[i] != '.'))
				i++;

			sysinfo_item_t *item =
			    (sysinfo_item_t *) slab_alloc(sysinfo_item_cache, FRAME_ATOMIC);
			if (!item)
				return NULL;

			cur->next = item;

			/* Fill in item name up to the delimiter */
			item->name = str_ndup(name, i);
			if (!item->name) {
				slab_free(sysinfo_item_cache, item);
				return NULL;
			}

			/* Create subtree items */
			if (name[i] == '.') {
				item->subtree_type = SYSINFO_SUBTREE_TABLE;
				sysinfo_item_t *sub = sysinfo_create_path(
				    name + i + 1, &(item->subtree.table));
				if (!sub) {
					free(item->name);
					slab_free(sysinfo_item_cache, item);
					return NULL;
				}
				return sub;
			}

			/* No subtree needs to be created */
			return item;
		}

		cur = cur->next;
	}

	unreachable();
}

/** Set sysinfo item with a constant numeric value
 *
 * @param name Sysinfo path.
 * @param root Pointer to the root item or where to store
 *             a new root item (NULL for global sysinfo root).
 * @param val  Value to store in the item.
 *
 */
void sysinfo_set_item_val(const char *name, sysinfo_item_t **root,
    sysarg_t val)
{
	/* Protect sysinfo tree consistency */
	mutex_lock(&sysinfo_lock);

	if (root == NULL)
		root = &global_root;

	sysinfo_item_t *item = sysinfo_create_path(name, root);
	if (item != NULL) {
		item->val_type = SYSINFO_VAL_VAL;
		item->val.val = val;
	} else {
		printf("Could not set sysinfo item %s.\n", name);
	}

	mutex_unlock(&sysinfo_lock);
}

/** Set sysinfo item with a constant binary data
 *
 * Note that sysinfo only stores the pointer to the
 * binary data and does not touch it in any way. The
 * data should be static and immortal.
 *
 * @param name Sysinfo path.
 * @param root Pointer to the root item or where to store
 *             a new root item (NULL for global sysinfo root).
 * @param data Binary data.
 * @param size Size of the binary data.
 *
 */
void sysinfo_set_item_data(const char *name, sysinfo_item_t **root,
    void *data, size_t size)
{
	/* Protect sysinfo tree consistency */
	mutex_lock(&sysinfo_lock);

	if (root == NULL)
		root = &global_root;

	sysinfo_item_t *item = sysinfo_create_path(name, root);
	if (item != NULL) {
		item->val_type = SYSINFO_VAL_DATA;
		item->val.data.data = data;
		item->val.data.size = size;
	} else {
		printf("Could not set sysinfo item %s.\n", name);
	}

	mutex_unlock(&sysinfo_lock);
}

/** Set sysinfo item with a generated numeric value
 *
 * @param name Sysinfo path.
 * @param root Pointer to the root item or where to store
 *             a new root item (NULL for global sysinfo root).
 * @param fn   Numeric value generator function.
 * @param data Private data.
 *
 */
void sysinfo_set_item_gen_val(const char *name, sysinfo_item_t **root,
    sysinfo_fn_val_t fn, void *data)
{
	/* Protect sysinfo tree consistency */
	mutex_lock(&sysinfo_lock);

	if (root == NULL)
		root = &global_root;

	sysinfo_item_t *item = sysinfo_create_path(name, root);
	if (item != NULL) {
		item->val_type = SYSINFO_VAL_FUNCTION_VAL;
		item->val.gen_val.fn = fn;
		item->val.gen_val.data = data;
	} else {
		printf("Could not set sysinfo item %s.\n", name);
	}

	mutex_unlock(&sysinfo_lock);
}

/** Set sysinfo item with a generated binary data
 *
 * Note that each time the generator function is called
 * it is supposed to return a new dynamically allocated
 * data. This data is then freed by sysinfo in the context
 * of the current sysinfo request.
 *
 * @param name Sysinfo path.
 * @param root Pointer to the root item or where to store
 *             a new root item (NULL for global sysinfo root).
 * @param fn   Binary data generator function.
 * @param data Private data.
 *
 */
void sysinfo_set_item_gen_data(const char *name, sysinfo_item_t **root,
    sysinfo_fn_data_t fn, void *data)
{
	/* Protect sysinfo tree consistency */
	mutex_lock(&sysinfo_lock);

	if (root == NULL)
		root = &global_root;

	sysinfo_item_t *item = sysinfo_create_path(name, root);
	if (item != NULL) {
		item->val_type = SYSINFO_VAL_FUNCTION_DATA;
		item->val.gen_data.fn = fn;
		item->val.gen_data.data = data;
	} else {
		printf("Could not set sysinfo item %s.\n", name);
	}

	mutex_unlock(&sysinfo_lock);
}

/** Set sysinfo item with an undefined value
 *
 * @param name Sysinfo path.
 * @param root Pointer to the root item or where to store
 *             a new root item (NULL for global sysinfo root).
 *
 */
void sysinfo_set_item_undefined(const char *name, sysinfo_item_t **root)
{
	/* Protect sysinfo tree consistency */
	mutex_lock(&sysinfo_lock);

	if (root == NULL)
		root = &global_root;

	sysinfo_item_t *item = sysinfo_create_path(name, root);
	if (item != NULL)
		item->val_type = SYSINFO_VAL_UNDEFINED;
	else
		printf("Could not set sysinfo item %s.\n", name);

	mutex_unlock(&sysinfo_lock);
}

/** Set sysinfo item with a generated subtree
 *
 * @param name Sysinfo path.
 * @param root Pointer to the root item or where to store
 *             a new root item (NULL for global sysinfo root).
 * @param fn   Subtree generator function.
 * @param data Private data to be passed to the generator.
 *
 */
void sysinfo_set_subtree_fn(const char *name, sysinfo_item_t **root,
    sysinfo_fn_subtree_t fn, void *data)
{
	/* Protect sysinfo tree consistency */
	mutex_lock(&sysinfo_lock);

	if (root == NULL)
		root = &global_root;

	sysinfo_item_t *item = sysinfo_create_path(name, root);

	/*
	 * Change the type of the subtree only if it is not already
	 * a fixed subtree
	 */
	if ((item != NULL) && (item->subtree_type != SYSINFO_SUBTREE_TABLE)) {
		item->subtree_type = SYSINFO_SUBTREE_FUNCTION;
		item->subtree.generator.fn = fn;
		item->subtree.generator.data = data;
	} else {
		printf("Could not set sysinfo item %s.\n", name);
	}

	mutex_unlock(&sysinfo_lock);
}

/** Sysinfo dump indentation helper routine
 *
 * @param depth Number of spaces to print.
 *
 */
_NO_TRACE static void sysinfo_indent(size_t spaces)
{
	for (size_t i = 0; i < spaces; i++)
		printf(" ");
}

/** Dump the structure of sysinfo tree
 *
 * Should be called with sysinfo_lock held.
 *
 * @param root   Root item of the current (sub)tree.
 * @param spaces Current indentation level.
 *
 */
_NO_TRACE static void sysinfo_dump_internal(sysinfo_item_t *root, size_t spaces)
{
	/* Walk all siblings */
	for (sysinfo_item_t *cur = root; cur; cur = cur->next) {
		size_t length;

		if (spaces == 0) {
			printf("%s", cur->name);
			length = str_length(cur->name);
		} else {
			sysinfo_indent(spaces);
			printf(".%s", cur->name);
			length = str_length(cur->name) + 1;
		}

		sysarg_t val;
		size_t size;

		/* Display node value and type */
		switch (cur->val_type) {
		case SYSINFO_VAL_UNDEFINED:
			printf(" [undefined]\n");
			break;
		case SYSINFO_VAL_VAL:
			printf(" -> %" PRIun " (%#" PRIxn ")\n", cur->val.val,
			    cur->val.val);
			break;
		case SYSINFO_VAL_DATA:
			printf(" (%zu bytes)\n", cur->val.data.size);
			break;
		case SYSINFO_VAL_FUNCTION_VAL:
			val = cur->val.gen_val.fn(cur, cur->val.gen_val.data);
			printf(" -> %" PRIun " (%#" PRIxn ") [generated]\n", val,
			    val);
			break;
		case SYSINFO_VAL_FUNCTION_DATA:
			/* N.B.: No data was actually returned (only a dry run) */
			(void) cur->val.gen_data.fn(cur, &size, true,
			    cur->val.gen_data.data);
			printf(" (%zu bytes) [generated]\n", size);
			break;
		default:
			printf("+ %s [unknown]\n", cur->name);
		}

		/* Recursivelly nest into the subtree */
		switch (cur->subtree_type) {
		case SYSINFO_SUBTREE_NONE:
			break;
		case SYSINFO_SUBTREE_TABLE:
			sysinfo_dump_internal(cur->subtree.table, spaces + length);
			break;
		case SYSINFO_SUBTREE_FUNCTION:
			sysinfo_indent(spaces + length);
			printf("<generated subtree>\n");
			break;
		default:
			sysinfo_indent(spaces + length);
			printf("<unknown subtree>\n");
		}
	}
}

/** Dump the structure of sysinfo tree
 *
 * @param root  Root item of the sysinfo (sub)tree.
 *              If it is NULL then consider the global
 *              sysinfo tree.
 *
 */
void sysinfo_dump(sysinfo_item_t *root)
{
	/*
	 * Avoid other functions to mess with sysinfo
	 * while we are dumping it
	 */
	mutex_lock(&sysinfo_lock);

	if (root == NULL)
		sysinfo_dump_internal(global_root, 0);
	else
		sysinfo_dump_internal(root, 0);

	mutex_unlock(&sysinfo_lock);
}

/** Return sysinfo item value determined by name
 *
 * Should be called with sysinfo_lock held.
 *
 * @param name    Sysinfo path.
 * @param root    Root item of the sysinfo (sub)tree.
 *                If it is NULL then consider the global
 *                sysinfo tree.
 * @param dry_run Do not actually get any generated
 *                binary data, just calculate the size.
 *
 * @return Item value (constant or generated).
 *
 */
_NO_TRACE static sysinfo_return_t sysinfo_get_item(const char *name,
    sysinfo_item_t **root, bool dry_run)
{
	if (root == NULL)
		root = &global_root;

	/* Try to find the item or generate data */
	sysinfo_return_t ret;
	sysinfo_return_t *ret_ptr = &ret;
	sysinfo_item_t *item = sysinfo_find_item(name, *root, &ret_ptr,
	    dry_run);

	if (item != NULL) {
		/* Item found in the fixed sysinfo tree */

		ret.tag = item->val_type;
		switch (item->val_type) {
		case SYSINFO_VAL_UNDEFINED:
			break;
		case SYSINFO_VAL_VAL:
			ret.val = item->val.val;
			break;
		case SYSINFO_VAL_DATA:
			ret.data = item->val.data;
			break;
		case SYSINFO_VAL_FUNCTION_VAL:
			ret.val = item->val.gen_val.fn(item, item->val.gen_val.data);
			break;
		case SYSINFO_VAL_FUNCTION_DATA:
			ret.data.data = item->val.gen_data.fn(item, &ret.data.size,
			    dry_run, item->val.gen_data.data);
			break;
		}
	} else {
		/* No item in the fixed sysinfo tree */
		if (ret_ptr == NULL) {
			/* Even no data was generated */
			ret.tag = SYSINFO_VAL_UNDEFINED;
		}
	}

	return ret;
}

/** Return sysinfo item determined by name from user space
 *
 * The path string passed from the user space has to be properly null-terminated
 * (the last passed character must be null).
 *
 * @param ptr     Sysinfo path in the user address space.
 * @param size    Size of the path string.
 * @param dry_run Do not actually get any generated
 *                binary data, just calculate the size.
 *
 */
_NO_TRACE static sysinfo_return_t sysinfo_get_item_uspace(uspace_addr_t ptr, size_t size,
    bool dry_run)
{
	sysinfo_return_t ret;
	ret.tag = SYSINFO_VAL_UNDEFINED;

	if (size > SYSINFO_MAX_PATH)
		return ret;

	// TODO: Change this so that allocation is not needed.
	char *path = malloc(size + 1);
	if (!path)
		return ret;

	if ((copy_from_uspace(path, ptr, size + 1) == 0) &&
	    (path[size] == 0)) {
		/*
		 * Prevent other functions from messing with sysinfo while we
		 * are reading it.
		 */
		mutex_lock(&sysinfo_lock);
		ret = sysinfo_get_item(path, NULL, dry_run);
		mutex_unlock(&sysinfo_lock);
	}

	free(path);
	return ret;
}

/** Return sysinfo keys determined by name
 *
 * Should be called with sysinfo_lock held.
 *
 * @param name    Sysinfo path.
 * @param root    Root item of the sysinfo (sub)tree.
 *                If it is NULL then consider the global
 *                sysinfo tree.
 * @param dry_run Do not actually get any generated
 *                binary data, just calculate the size.
 *
 * @return Item value (constant or generated).
 *
 */
_NO_TRACE static sysinfo_return_t sysinfo_get_keys(const char *name,
    sysinfo_item_t **root, bool dry_run)
{
	if (root == NULL)
		root = &global_root;

	sysinfo_item_t *subtree = NULL;

	if (name[0] != 0) {
		/* Try to find the item */
		sysinfo_item_t *item =
		    sysinfo_find_item(name, *root, NULL, dry_run);
		if ((item != NULL) &&
		    (item->subtree_type == SYSINFO_SUBTREE_TABLE))
			subtree = item->subtree.table;
	} else
		subtree = *root;

	sysinfo_return_t ret;
	ret.tag = SYSINFO_VAL_UNDEFINED;

	if (subtree != NULL) {
		/*
		 * Calculate the size of subkeys.
		 */
		size_t size = 0;
		for (sysinfo_item_t *cur = subtree; cur; cur = cur->next)
			size += str_size(cur->name) + 1;

		if (dry_run) {
			ret.tag = SYSINFO_VAL_DATA;
			ret.data.data = NULL;
			ret.data.size = size;
		} else {
			/* Allocate buffer for subkeys */
			char *names = (char *) malloc(size);
			if (names == NULL)
				return ret;

			size_t pos = 0;
			for (sysinfo_item_t *cur = subtree; cur; cur = cur->next) {
				str_cpy(names + pos, size - pos, cur->name);
				pos += str_size(cur->name) + 1;
			}

			/* Correct return value */
			ret.tag = SYSINFO_VAL_DATA;
			ret.data.data = (void *) names;
			ret.data.size = size;
		}
	}

	return ret;
}

/** Return sysinfo keys determined by name from user space
 *
 * The path string passed from the user space has to be properly
 * null-terminated (the last passed character must be null).
 *
 * @param ptr     Sysinfo path in the user address space.
 * @param size    Size of the path string.
 * @param dry_run Do not actually get any generated
 *                binary data, just calculate the size.
 *
 */
_NO_TRACE static sysinfo_return_t sysinfo_get_keys_uspace(uspace_addr_t ptr, size_t size,
    bool dry_run)
{
	sysinfo_return_t ret;
	ret.tag = SYSINFO_VAL_UNDEFINED;
	ret.data.data = NULL;
	ret.data.size = 0;

	if (size > SYSINFO_MAX_PATH)
		return ret;

	// TODO: Change this so that allocation is not needed.
	char *path = malloc(size + 1);
	if (!path)
		return ret;

	if ((copy_from_uspace(path, ptr, size + 1) == 0) &&
	    (path[size] == 0)) {
		/*
		 * Prevent other functions from messing with sysinfo while we
		 * are reading it.
		 */
		mutex_lock(&sysinfo_lock);
		ret = sysinfo_get_keys(path, NULL, dry_run);
		mutex_unlock(&sysinfo_lock);
	}

	free(path);
	return ret;
}

/** Get the sysinfo keys size (syscall)
 *
 * The path string passed from the user space has
 * to be properly null-terminated (the last passed
 * character must be null).
 *
 * @param path_ptr  Sysinfo path in the user address space.
 * @param path_size Size of the path string.
 * @param size_ptr  User space pointer where to store the
 *                  keys size.
 *
 * @return Error code (EOK in case of no error).
 *
 */
sys_errno_t sys_sysinfo_get_keys_size(uspace_addr_t path_ptr, size_t path_size,
    uspace_addr_t size_ptr)
{
	errno_t rc;

	/*
	 * Get the keys.
	 *
	 * N.B.: There is no need to free any potential keys
	 * since we request a dry run.
	 */
	sysinfo_return_t ret =
	    sysinfo_get_keys_uspace(path_ptr, path_size, true);

	/* Check return data tag */
	if (ret.tag == SYSINFO_VAL_DATA)
		rc = copy_to_uspace(size_ptr, &ret.data.size,
		    sizeof(ret.data.size));
	else
		rc = EINVAL;

	return (sys_errno_t) rc;
}

/** Get the sysinfo keys (syscall)
 *
 * The path string passed from the user space has
 * to be properly null-terminated (the last passed
 * character must be null).
 *
 * If the user space buffer size does not equal
 * the actual size of the returned data, the data
 * is truncated.
 *
 * The actual size of data returned is stored to
 * size_ptr.
 *
 * @param path_ptr    Sysinfo path in the user address space.
 * @param path_size   Size of the path string.
 * @param buffer_ptr  User space pointer to the buffer where
 *                    to store the binary data.
 * @param buffer_size User space buffer size.
 * @param size_ptr    User space pointer where to store the
 *                    binary data size.
 *
 * @return Error code (EOK in case of no error).
 *
 */
sys_errno_t sys_sysinfo_get_keys(uspace_addr_t path_ptr, size_t path_size,
    uspace_addr_t buffer_ptr, size_t buffer_size, uspace_ptr_size_t size_ptr)
{
	errno_t rc;

	/* Get the keys */
	sysinfo_return_t ret = sysinfo_get_keys_uspace(path_ptr, path_size,
	    false);

	/* Check return data tag */
	if (ret.tag == SYSINFO_VAL_DATA) {
		size_t size = min(ret.data.size, buffer_size);
		rc = copy_to_uspace(buffer_ptr, ret.data.data, size);
		if (rc == EOK)
			rc = copy_to_uspace(size_ptr, &size, sizeof(size));

		free(ret.data.data);
	} else
		rc = EINVAL;

	return (sys_errno_t) rc;
}

/** Get the sysinfo value type (syscall)
 *
 * The path string passed from the user space has
 * to be properly null-terminated (the last passed
 * character must be null).
 *
 * @param path_ptr  Sysinfo path in the user address space.
 * @param path_size Size of the path string.
 *
 * @return Item value type.
 *
 */
sysarg_t sys_sysinfo_get_val_type(uspace_addr_t path_ptr, size_t path_size)
{
	/*
	 * Get the item.
	 *
	 * N.B.: There is no need to free any potential generated
	 * binary data since we request a dry run.
	 */
	sysinfo_return_t ret = sysinfo_get_item_uspace(path_ptr, path_size, true);

	/*
	 * Map generated value types to constant types (user space does
	 * not care whether the value is constant or generated).
	 */
	if (ret.tag == SYSINFO_VAL_FUNCTION_VAL)
		ret.tag = SYSINFO_VAL_VAL;
	else if (ret.tag == SYSINFO_VAL_FUNCTION_DATA)
		ret.tag = SYSINFO_VAL_DATA;

	return (sysarg_t) ret.tag;
}

/** Get the sysinfo numerical value (syscall)
 *
 * The path string passed from the user space has
 * to be properly null-terminated (the last passed
 * character must be null).
 *
 * @param path_ptr  Sysinfo path in the user address space.
 * @param path_size Size of the path string.
 * @param value_ptr User space pointer where to store the
 *                  numberical value.
 *
 * @return Error code (EOK in case of no error).
 *
 */
sys_errno_t sys_sysinfo_get_value(uspace_addr_t path_ptr, size_t path_size,
    uspace_addr_t value_ptr)
{
	errno_t rc;

	/*
	 * Get the item.
	 *
	 * N.B.: There is no need to free any potential generated binary
	 * data since we request a dry run.
	 */
	sysinfo_return_t ret = sysinfo_get_item_uspace(path_ptr, path_size, true);

	/* Only constant or generated numerical value is returned */
	if ((ret.tag == SYSINFO_VAL_VAL) || (ret.tag == SYSINFO_VAL_FUNCTION_VAL))
		rc = copy_to_uspace(value_ptr, &ret.val, sizeof(ret.val));
	else
		rc = EINVAL;

	return (sys_errno_t) rc;
}

/** Get the sysinfo binary data size (syscall)
 *
 * The path string passed from the user space has
 * to be properly null-terminated (the last passed
 * character must be null).
 *
 * @param path_ptr  Sysinfo path in the user address space.
 * @param path_size Size of the path string.
 * @param size_ptr  User space pointer where to store the
 *                  binary data size.
 *
 * @return Error code (EOK in case of no error).
 *
 */
sys_errno_t sys_sysinfo_get_data_size(uspace_addr_t path_ptr, size_t path_size,
    uspace_addr_t size_ptr)
{
	errno_t rc;

	/*
	 * Get the item.
	 *
	 * N.B.: There is no need to free any potential generated binary
	 * data since we request a dry run.
	 */
	sysinfo_return_t ret = sysinfo_get_item_uspace(path_ptr, path_size, true);

	/* Only the size of constant or generated binary data is considered */
	if ((ret.tag == SYSINFO_VAL_DATA) || (ret.tag == SYSINFO_VAL_FUNCTION_DATA))
		rc = copy_to_uspace(size_ptr, &ret.data.size,
		    sizeof(ret.data.size));
	else
		rc = EINVAL;

	return (sys_errno_t) rc;
}

/** Get the sysinfo binary data (syscall)
 *
 * The path string passed from the user space has
 * to be properly null-terminated (the last passed
 * character must be null).
 *
 * If the user space buffer size does not equal
 * the actual size of the returned data, the data
 * is truncated. Whether this is actually a fatal
 * error or the data can be still interpreted as valid
 * depends on the nature of the data and has to be
 * decided by the user space.
 *
 * The actual size of data returned is stored to
 * size_ptr.
 *
 * @param path_ptr    Sysinfo path in the user address space.
 * @param path_size   Size of the path string.
 * @param buffer_ptr  User space pointer to the buffer where
 *                    to store the binary data.
 * @param buffer_size User space buffer size.
 * @param size_ptr    User space pointer where to store the
 *                    binary data size.
 *
 * @return Error code (EOK in case of no error).
 *
 */
sys_errno_t sys_sysinfo_get_data(uspace_addr_t path_ptr, size_t path_size,
    uspace_addr_t buffer_ptr, size_t buffer_size, uspace_ptr_size_t size_ptr)
{
	errno_t rc;

	/* Get the item */
	sysinfo_return_t ret = sysinfo_get_item_uspace(path_ptr, path_size,
	    false);

	/* Only constant or generated binary data is considered */
	if ((ret.tag == SYSINFO_VAL_DATA) ||
	    (ret.tag == SYSINFO_VAL_FUNCTION_DATA)) {
		size_t size = min(ret.data.size, buffer_size);
		rc = copy_to_uspace(buffer_ptr, ret.data.data, size);
		if (rc == EOK)
			rc = copy_to_uspace(size_ptr, &size, sizeof(size));
	} else
		rc = EINVAL;

	/* N.B.: The generated binary data should be freed */
	if ((ret.tag == SYSINFO_VAL_FUNCTION_DATA) && (ret.data.data != NULL))
		free(ret.data.data);

	return (sys_errno_t) rc;
}

/** @}
 */
